﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using Needle.Utils.Extensions;
using Needle.Utils.Parser;
using System.Reflection;
namespace Needle.Templates
{

    using TokenNode = LinkedListNode<TemplateToken> ;
    using TokenList = LinkedList<TemplateToken>;

    public partial class TemplateParser : Component
    {

        #region Constants

        private const int DEFAULT_OUTPUT_BUFFER_SIZE = 4096;

        #endregion

        #region private fields

        private TokenList _Tokens;
        private TokenNode _CurrentNode;
   
        #endregion

        #region private properties...

        private TokenNode PreviousNode
        {
            get
            {
                if (_CurrentNode == null)
                    return null;
                return _CurrentNode.Previous;
            }
        }

        private TokenNode CurrentNode
        {
            get { return _CurrentNode; }
        }

        private TokenNode NextNode
        {
            get
            {
                if (_CurrentNode == null)
                    return null;
                return _CurrentNode.Next;
            }
        }

        private TemplateToken PreviousToken
        {
            get
            {
                if (_CurrentNode == null || _CurrentNode.Previous == null)
                    return null;
                return _CurrentNode.Previous.Value;
            }
        }

        private TemplateToken CurrentToken
        {
            get
            {
                if (_CurrentNode == null)
                    return null;
                return _CurrentNode.Value;
            }
        }

        private TemplateToken NextToken
        {
            get
            {
                if (_CurrentNode == null || _CurrentNode.Next == null)
                    return null;
                return _CurrentNode.Next.Value;
            }
        }

        #endregion

        #region Constructors

        public TemplateParser()
        {
            InitializeComponent();
        }

        public TemplateParser(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
        }

        #endregion

        private void Initialize(TextReader input)
        {
            _Tokens = new TokenList(new TemplateTokenizer(input));
            Options = new TemplateOptions();
            MoveFirst();
        }

        public string Parse(string input)
        {
            if (String.IsNullOrEmpty(input))
                throw new ArgumentException("input is null or empty.", "input");
            
            StringWriter output = new StringWriter();
            Parse(new StringReader(input), output);
            return output.ToString();
        }

        public void Parse(TextReader input, TextWriter output)
        {
            if (input == null)
                throw new ArgumentNullException("input", "input is null.");
            if (output == null)
                throw new ArgumentNullException("output", "output is null.");

            Initialize(input);
            StripWhitespace();
            ValidateCodeSectionTermination();
            ParseTemplateMetadata();
            ParseCodeSections();
            ParseTextSections();
            
            foreach (var name_space in Options.ImportedNamespaces)
                output.WriteLine(String.Format("using {0};", name_space));

            output.Write("public class Template : TemplateBase\r\n{\r\n\tpublic Template()\r\n\t{ }\r\n\tpublic void Write(object value)\r\n\t{ }");
            
            foreach (var prop in Options.Properties)
                output.Write(String.Format("\r\n\tpublic {0} {1} {{ get; set; }}", prop.Type.FullName, prop.Name));

            output.Write("\r\n\tpublic void Generate()\r\n\t{\r\n\t");
            foreach (TemplateToken token in _Tokens)
            {
                TemplateTokenType type = token.TemplateType;
                switch (type)
                {
                    case TemplateTokenType.Code:
                        output.WriteLine(token.Value);
                        break;
                    case TemplateTokenType.Text:
                    case TemplateTokenType.None:
                    case TemplateTokenType.EndText:
                    case TemplateTokenType.StartText:
                    case TemplateTokenType.EOF:
                        
                        break;
                }
            }

            output.Write("\r\n\t}\r\n}");
      
        }

        #region public properties...

        public TemplateOptions Options { get; set; }

        #endregion

        #region Parsing

        #region Whitespace parsing

        private void StripWhitespace()
        {
            if (MoveFirst())
            {
                do
                {
                    if (NextToken != null && NextToken.TemplateType == TemplateTokenType.Metadata && NextToken.Type == TokenType.WhiteSpace)
                    {
                        StripFollowingWhitespace(true);
                    }
                    else if (CurrentToken.TemplateType == TemplateTokenType.EndText)
                    {
                        if (NextToken != null && NextToken.Value.ToLower().StartsWith("x"))
                        {
                            DeleteNextNode();
                            StripPrecedingWhitespace(NextToken.Value.StartsWith("X"));
                        }
                    }
                    else if (CurrentToken.TemplateType == TemplateTokenType.StartText)
                    {
                        if (PreviousToken != null)
                        {
                            if (PreviousToken.Value.ToLower().EndsWith("x"))
                            {
                                DeletePreviousNode();
                                StripFollowingWhitespace(PreviousToken.Value.EndsWith("X"));
                            }
                            if (PreviousToken.TemplateType == TemplateTokenType.Metadata)
                                StripFollowingWhitespace(true);
                        }
                    }

                } while (MoveNext());
            }
        }

        private void StripFollowingWhitespace(bool deleteNewLine)
        {
            while (NextToken != null && NextToken.Type == TokenType.WhiteSpace)
                DeleteNextNode();

            if (deleteNewLine && NextToken != null && NextToken.Type == TokenType.NewLine)
                DeleteNextNode();

        }

        private void StripPrecedingWhitespace(bool deleteNewLine)
        {
            while (PreviousToken != null && PreviousToken.Type == TokenType.WhiteSpace)
                DeletePreviousNode();

            if (deleteNewLine && PreviousToken != null && PreviousToken.Type == TokenType.NewLine)
                DeletePreviousNode();
        }

        #endregion

        #region Metadata section parsing

        private void ParseTemplateMetadata()
        {
            MoveFirst();
            do
            {
                if (CurrentToken.TemplateType == TemplateTokenType.EndText
                    && NextToken.TemplateType == TemplateTokenType.Metadata)
                {
                    var startNode = CurrentNode;
                    ReadToken("T");
                    switch (ReadToken())
                    {
                        case "language":
                            ParseLanguage();
                            break;
                        case "output":
                            ParseOutputOptions();
                            break;
                        case "import":
                            ParseImports();
                            break;
                        case "property":
                            ParseProperty();
                            break;
                        default:
                            break;
                    }
                    MoveTo(startNode);
                    while (CurrentToken.TemplateType != TemplateTokenType.StartText && DeleteCurrentNode()) ;
                    DeleteCurrentNode();
                }
            } while (MoveNext());
        }

        private void ParseProperty()
        {
            ValidateCurrentTokenIs("property");
            string name = null;
            Type type = null;
            object initialValue = null;
            bool hasInitalValue = false;
            do
            {
                switch (ReadToken())
                {
                    case "name":
                        ReadEqualsToken();
                        name = ReadQuotedString();
                        break;
                    case "type":
                        ReadEqualsToken();
                        string typeName = ReadQuotedString();
                        try
                        { type = Type.GetType(typeName, true); }
                        catch (Exception ex)
                        { throw new ParseException(CurrentToken, ex.Message); }
                        break;
                    case "value":
                        ReadEqualsToken();
                        string initialValueString = ReadQuotedString();
                        try
                        {
                            TypeConverter converter = TypeDescriptor.GetConverter(type);
                            initialValue = converter.ConvertFromString(initialValueString);
                            hasInitalValue = true;
                        }
                        catch (Exception ex)
                        { throw new ParseException(CurrentToken, ex.Message); }
                        break;
                    default:
                        if (CurrentToken.TemplateType == TemplateTokenType.StartText)
                            break;
                        throw new ParseException(CurrentToken, "Unexpected value");
                }
            } while (CurrentToken.TemplateType != TemplateTokenType.StartText);
            if (hasInitalValue)
                Options.Properties.Add(new Property(name, type, initialValue));
            else
                Options.Properties.Add(new Property(name, type));
        }

        private void ParseImports()
        {
            ValidateCurrentTokenIs("import");
            do
            {
                switch (ReadToken())
                {

                    case "namespace":
                        ReadEqualsToken();
                        Options.ImportedNamespaces.Add(ReadQuotedString());
                        break;
                    case "dll":
                        ReadEqualsToken();
                        try
                        {
                            var asmName = new AssemblyName(ReadQuotedString());
                            Options.ReferencedAssemblies.Add(asmName);
                        }
                        catch (Exception ex)
                        { throw new ParseException(CurrentToken, ex.Message); }
                        break;
                    default:
                        if (CurrentToken.TemplateType == TemplateTokenType.StartText)
                            break;
                        throw new ParseException(CurrentToken, "Unexpected value");
                }
            } while (CurrentToken.TemplateType != TemplateTokenType.StartText);
        }

        private void ParseOutputOptions()
        {
            ValidateCurrentTokenIs("output");
            do
            {
                switch (ReadToken())
                {
                    case "name":
                        ReadEqualsToken();
                        MoveNext();
                        Options.Output.Name = CurrentToken.Value.Trim('"');
                        break;
                    case "encoding":
                        ReadEqualsToken();
                        try { Options.Output.Encoding = Encoding.GetEncoding(ReadQuotedString()); }
                        catch (ArgumentException ex)
                        { throw new ParseException(CurrentToken, ex.Message); }
                        break;
                    default:
                        if (CurrentToken.TemplateType == TemplateTokenType.StartText)
                            break;
                        throw new ParseException(CurrentToken, "Unexpected value");
                }
            } while (CurrentToken.TemplateType != TemplateTokenType.StartText);
        }

        private void ParseLanguage()
        {
            ValidateCurrentTokenIs("language");
            do
            {
                switch (ReadToken())
                {
                    case "name":
                        ReadEqualsToken();
                        Options.Language = ReadQuotedString();
                        break;
                    default:
                        if (CurrentToken.TemplateType == TemplateTokenType.StartText)
                            break;
                        throw new ParseException(CurrentToken, "Unexpected value");
                }
            } while (CurrentToken.TemplateType != TemplateTokenType.StartText);
        }

        #endregion

        #region Code section parsing

        private void ParseCodeSections()
        {
            var context = new TokenContext();
            MoveFirst();
            do
            {
                if (CurrentToken.TemplateType == TemplateTokenType.Code)
                {
                    using (var ctx = context.Save(CurrentToken))
                    {
                        AppendStartCode(ctx);
                        while (CurrentToken.TemplateType == TemplateTokenType.Code)
                        {
                            ctx.Buffer.Append(CurrentToken.Value);
                            if (!DeleteCurrentNode())
                                break;
                        }
                        AppendEndCode(ctx);
                        TemplateToken token = new TemplateToken(ctx.Buffer.ToString(), TokenType.Token, TemplateTokenType.Code, ctx.Line, ctx.Column, ctx.Index);
                        InsertToken(token);
                    }
                }
            } while (MoveNext());
        }

        #endregion

        #region Text section parsing

        private void ParseTextSections()
        {
            var context = new TokenContext();
            MoveFirst();
            do
            {
                if (CurrentToken.TemplateType == TemplateTokenType.Text)
                {
                    using (var ctx = context.Save(CurrentToken))
                    {
                        AppendStartText(ctx);
                        while (CurrentToken.TemplateType == TemplateTokenType.Text)
                        {
                            AppendEscapedText(ctx, CurrentToken.Value);
                            if (!DeleteCurrentNode())
                                break;
                        }
                        AppendEndText(ctx);
                        TemplateToken token = new TemplateToken(ctx.Buffer.Flush(), TokenType.Token, TemplateTokenType.Code, ctx.Line, ctx.Column, ctx.Index);
                        InsertToken(token);
                    }
                }
            } while (MoveNext());

        }

        #endregion

        #region helpers

        private void ReadToken(string token)
        {
            ReadToken(token, true);
        }

        private void ReadEqualsToken()
        {
            ReadToken("=", true);
        }

        private string ReadToken(string token, bool throwOnMismatch)
        {
            MoveNext();
            if (throwOnMismatch && CurrentToken.Value != token)
                throw new ParseException(CurrentToken, String.Format("Expected '{0}' but found: '{0}'", token, CurrentToken.Value));
            return CurrentToken.Value;
        }

        private string ReadToken()
        {
            return ReadToken(null, false);
        }

        private string ReadQuotedString()
        {
            MoveNext();
            string val = CurrentToken.Value;
            if (!val.StartsWith("\"") && val.EndsWith("\""))
                throw new ParseException(CurrentToken, "Expected a quoted string");
            return CurrentToken.Value.Trim('"');
        }

        #endregion

        #endregion

        #region validation

        private void ValidateCurrentTokenIs(string p)
        {
            if (CurrentToken == null || CurrentToken.Value != p)
                throw new ParseException(CurrentToken, String.Format("Expected {0}.", p));
        }

        private void ValidateCodeSectionTermination()
        {
            MoveFirst();
            var stack = new Stack<TokenNode>();
            do
            {
                if (CurrentToken.TemplateType == TemplateTokenType.EndText)
                    stack.Push(CurrentNode);
                else if (CurrentToken.TemplateType == TemplateTokenType.StartText)
                {
                    stack.Pop();
                    ValidateStackEmpty(stack);
                }
            } while (MoveNext());
            ValidateStackEmpty(stack);
        }

        private void ValidateStackEmpty(Stack<TokenNode> stack)
        {
            if (stack.Count > 0)
            {
                while (stack.Count > 1)
                    stack.Pop();
                var firstUnterminatedCodeSection = stack.Pop() ;
                MoveTo(firstUnterminatedCodeSection);
                string sectionType;
                if (NextToken != null && NextToken.TemplateType == TemplateTokenType.Metadata)
                    sectionType = "metadata";
                else
                    sectionType = "code";
                throw new ParseException(CurrentToken, String.Format("Unterminated {0} section", sectionType));
            }
        }
        
        #endregion

        #region Move and edit

        private bool InsertToken(TemplateToken token)
        {
            if (_CurrentNode == null)
                _Tokens.AddLast(token);
            else
                _Tokens.AddBefore(_CurrentNode, token);
            return true;
        } 
        /// <summary>
        /// returns true if the operation succeeds
        /// </summary>
        private bool DeletePreviousNode()
        {
            if (PreviousNode != null)
            {
                _Tokens.Remove(PreviousNode);
                return true;
            }
            return false;
        }

        /// <summary>
        /// returns true if the operation succeeds
        /// </summary>
        private bool MoveLast()
        {
            if (_Tokens.Count == 0)
                return false;
            _CurrentNode = _Tokens.Last;
            return true;
        }

        /// <summary>
        /// returns true if the operation succeeds
        /// </summary>
        private bool MoveFirst()
        {
            if (_Tokens.Count == 0)
                return false;
            _CurrentNode = _Tokens.First;
            return true;
        }

        /// <summary>
        /// returns true if the operation succeeds
        /// </summary>
        private bool MoveTo(TokenNode node)
        {
            if (node == null)
                throw new ArgumentNullException("node", "node is null.");
            if (node.List != _Tokens)
                throw new InvalidOperationException("Cannot move to a deleted node");
            _CurrentNode = node;
            return true;
        }

        /// <summary>
        /// returns true if the operation succeeds
        /// </summary>
        private bool DeleteCurrentNode()
        {
            if (MoveNext())
                return DeletePreviousNode();
            else if (MovePrevious())
                return DeleteNextNode();
            else
                return false;
        }

        /// <summary>
        /// returns true if the operation succeeds
        /// </summary>
        private bool DeleteNextNode()
        {
            if (NextNode != null)
            {
                _Tokens.Remove(NextNode);
                return true;
            }
            return false;
        }

        /// <summary>
        /// returns true if the operation succeeds
        /// </summary>
        private bool MoveNext()
        {
            if (_CurrentNode.Next == null)
                return false;
            _CurrentNode = _CurrentNode.Next;
            return true;
        }

        /// <summary>
        /// returns true if the operation succeeds
        /// </summary>
        private bool MovePrevious()
        {
            if (_CurrentNode.Previous == null)
                return false;
            _CurrentNode = _CurrentNode.Previous;
            return true;
        }

        #endregion

        #region Inner types

        private enum CodeType
        {
            Code,
            Directive,
            Expression,
        }

        private class TokenContext : IDisposable 
        {

            public TokenContext()
            {
                Buffer = new StringBuilder();
                Reset();
            }

            public TokenContext Save(StringToken token)
            {
                Line = token.Line;
                Column = token.Column;
                Index = token.Index;
                UserState = token.UserState;
                return this;
            }

            public void Reset()
            {
                Line = 0;
                Index = 0;
                Column = 0;
                Buffer.Length = 0;
                UserState = null;
            }

            public int Line { get; private set; }
            public int Column { get; private set; }
            public int Index { get; private set; }
            public object UserState { get; private set; }

            public StringBuilder Buffer { get; private set; }
        
            public void Dispose()
            {
                Reset();
            }

        }

        #endregion

        #region Output
        
        // TODO factor out some sort of language provider

        private void AppendStartText(TokenContext ctx)
        {
            ctx.Buffer.Append("Write(@\"");
        }

        private void AppendEscapedText(TokenContext ctx, string p)
        {
            ctx.Buffer.Append(p.Replace("\"", "\"\""));
        }

        private void AppendEndText(TokenContext ctx)
        {
            ctx.Buffer.AppendFormat("\"); // [{0:000},{1:000}]", ctx.Line, ctx.Column);
        }

        private void AppendStartCode(TokenContext ctx)
        {
            if (CurrentToken != null 
                && PreviousToken != null 
                && CurrentToken.Value == "=" 
                && PreviousToken.TemplateType == TemplateTokenType.EndText)
            {
                DeleteCurrentNode();
                _inStringExpressionWrite = true;
                ctx.Buffer.Append("Write(");
            }
        }

        private bool _inStringExpressionWrite;
        private void AppendProperty(TokenContext ctx, Type propertyType, string propertyName)
        {
            ctx.Buffer.AppendFormat("public {0} {1} { get; set; }\n", propertyType.FullName, propertyName);
        }

        private void AppendEndCode(TokenContext ctx)
        {
            if (_inStringExpressionWrite)
            {
                _inStringExpressionWrite = false;
                ctx.Buffer.Append(");");
            }
            ctx.Buffer.AppendFormat(" // [{0:000},{1:000}]", ctx.Line, ctx.Column);
        }

        #endregion
    }
}